/*
 * Copyright (c) 2006 Intel Corporation
 * All rights reserved.
 *
 * This file is distributed under the terms in the attached INTEL-LICENSE     
 * file. If you do not find these files, copies can be found by writing to
 * Intel Research Berkeley, 2150 Shattuck Avenue, Suite 1300, Berkeley, CA, 
 * 94704.  Attention:  Intel License Inquiry.
 */

import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

/* The main GUI object. Build the GUI and coordinate all user activities */
class Window {
	ContourTracking parent;
	Graph graph;
	MoteGrid grid;
	Bulletin bulletin;

	Font smallFont = new Font("Dialog", Font.PLAIN, 8);
	Font boldFont = new Font("Dialog", Font.BOLD, 12);
	Font normalFont = new Font("Dialog", Font.PLAIN, 12);
	MoteTableModel moteListModel; // GUI view of mote list
	JLabel xLabel; // Label displaying X axis range
	JTextField sampleText, thresholdText, yText; // inputs for sample period and Y axis range
	JFrame frame;

	Window(ContourTracking parent) {
		this.parent = parent;
	}

	/* A model for the mote table, and general utility operations on the mote
		 list */
	class MoteTableModel extends AbstractTableModel {
		private ArrayList motes = new ArrayList();
		private ArrayList colors = new ArrayList();

		/* Initial mote colors cycle through this list. Add more colors if
			 you want. */
		private Color[] cycle = {
			Color.RED, Color.PINK, Color.GREEN, Color.MAGENTA,
			Color.CYAN, Color.BLUE, Color.ORANGE,
		};
		int cycleIndex;

		/* TableModel methods for achieving our table appearance */
		public String getColumnName(int col) {
			if (col == 0) {
				return "Mote";
			} else {
				return "Color";
			}
		}
		public int getColumnCount() { return 2; }
		public synchronized int getRowCount() { return motes.size(); }
		public synchronized Object getValueAt(int row, int col) {
			if (col == 0) {
				return motes.get(row);
			} else {
				return colors.get(row);
			}
		}
		public Class getColumnClass(int col) {
			return getValueAt(0, col).getClass();
		}
		public boolean isCellEditable(int row, 
				int col) { return col == 1; }
		public synchronized void setValueAt(Object value, int row, int col) {
			colors.set(row, value);
			fireTableCellUpdated(row, col);
			graph.repaint();
			grid.repaint();
		}

		/* Return mote id of i'th mote */
		int get(int i) { return ((Integer)motes.get(i)).intValue(); }

		/* Return color of i'th mote */
		Color getColor(int i)  { return (Color)colors.get(i); }

		/* Return number of motes */
		int size() { return motes.size(); }

		/* Add a new mote */
		synchronized void newNode(int nodeId) {
			/* Shock, horror. No binary search. */
			int i, len = motes.size();

			for (i = 0; ; i++) {
				if (i == len || nodeId < get(i)) {
					motes.add(i, new Integer(nodeId));
					// Cycle through a set of initial colors
					colors.add(i, cycle[cycleIndex++ % cycle.length]);
					break;
				}
			}
			fireTableRowsInserted(i, i);
		}

		/* Remove all motes */
		void clear() {
			motes = new ArrayList();
			colors = new ArrayList();
			fireTableDataChanged();
		}
	}

	/* A simple full-color cell */
	static class MoteColor extends JLabel implements TableCellRenderer {
		public MoteColor() { setOpaque(true); }
		public Component getTableCellRendererComponent
			(JTable table, Object color,
			 boolean isSelected, boolean hasFocus, 
			 int row, int column) {
				setBackground((Color)color);
				return this;
			}
	}

	/* Convenience methods for making buttons, labels and textfields.
		 Simplifies code and ensures a consistent style. */

	JButton makeButton(String label, ActionListener action) {
		JButton button = new JButton();
		button.setText(label);
		button.setFont(boldFont);
		button.addActionListener(action);
		return button;
	}

	JLabel makeLabel(String txt, int alignment) {
		JLabel label = new JLabel(txt, alignment);
		label.setFont(boldFont);
		return label;
	}

	JLabel makeSmallLabel(String txt, int alignment) {
		JLabel label = new JLabel(txt, alignment);
		label.setFont(smallFont);
		return label;
	}

	JTextField makeTextField(int columns, ActionListener action) {
		JTextField tf = new JTextField(columns);
		tf.setFont(normalFont);
		tf.setMaximumSize(tf.getPreferredSize());
		tf.addActionListener(action);
		return tf;
	}

	public void showText(String text) {
		bulletin.setText(text);
	}

	/* Build the GUI */
	void setup() {
		JPanel main = new JPanel(new BorderLayout());

		main.setMinimumSize(new Dimension(500, 250));
		main.setPreferredSize(new Dimension(800, 600));

		// Three panels: mote list, graph, controls
		moteListModel = new  MoteTableModel();
		JTable moteList = new JTable(moteListModel);
		moteList.setDefaultRenderer(Color.class, new MoteColor());
		moteList.setDefaultEditor(Color.class, new ColorCellEditor("Pick Mote Color"));
		moteList.setPreferredScrollableViewportSize(new Dimension(100, 400));
		JScrollPane motePanel = new JScrollPane();
		motePanel.getViewport().add(moteList, null);
		main.add(motePanel, BorderLayout.WEST);

		graph = new Graph(this);
		grid = new MoteGrid(this);
		bulletin = new Bulletin();
		Box info = new Box(BoxLayout.Y_AXIS);
		info.add(bulletin);
		info.add(grid);
		Box charts = new Box(BoxLayout.X_AXIS);
		charts.add(info);
		charts.add(graph);
		main.add(charts, BorderLayout.CENTER);
		showText("No Event");

		// Controls. Organised using box layouts.

		// Sample period.
		JLabel sampleLabel = makeLabel("Sampling Period(ms):", JLabel.LEFT);
		sampleText = makeTextField(6, new ActionListener() {
				public void actionPerformed(ActionEvent e) { setSamplePeriod(); }
				} );
		updateSamplePeriod();

		// Contour Threshold
		JLabel thresholdLabel = makeLabel("Threshold:", JLabel.RIGHT);
		thresholdText = makeTextField(6, new ActionListener() {
				public void actionPerformed(ActionEvent e) { setSampleThreshold(); }
		} );
		updateSampleThreshold();

		// Clear data.
		JButton clearButton = makeButton("Clear", new ActionListener() {
				public void actionPerformed(ActionEvent e) { clearData(); }
				} );

		// Record snapshots.
		JButton recButton = makeButton("Record", new ActionListener() {
			public void actionPerformed(ActionEvent e) { 
				if(e.getActionCommand() == "Record") {
					parent.startRecording(); 
					((JButton)e.getSource()).setText("Stop");
				} else {
					parent.stopRecording();
					((JButton)e.getSource()).setText("Record");
				}
			}
		} );

		// Play snapshots.
		JButton playButton = makeButton("Play", new ActionListener() {
			public void actionPerformed(ActionEvent e) { 
				new EventContourTrackingPlayer().show();
			}
		} );

		// Adjust X-axis zoom.
		Box xControl = new Box(BoxLayout.Y_AXIS);
		xLabel = makeLabel("", JLabel.CENTER);
		final JSlider xSlider = new JSlider(JSlider.HORIZONTAL, 0, 8, graph.scale);
		Hashtable xTable = new Hashtable();
		for (int i = 0; i <= 8; i += 2) {
			xTable.put(new Integer(i),
					makeSmallLabel("" + (Graph.MIN_WIDTH << i),
						JLabel.CENTER));
		}
		xSlider.setLabelTable(xTable);
		xSlider.setPaintLabels(true);
		graph.updateXLabel();
		graph.setScale(graph.scale);
		xSlider.addChangeListener(new ChangeListener() {
				public void stateChanged(ChangeEvent e) {
				//if (!xSlider.getValueIsAdjusting())
				graph.setScale((int)xSlider.getValue());
				}
				});
		xControl.add(xLabel);
		xControl.add(xSlider);

		// Adjust Y-axis range.
		JLabel yLabel = makeLabel("Y:", JLabel.RIGHT);
		yText = makeTextField(12, new ActionListener() {
				public void actionPerformed(ActionEvent e) { setYAxis(); }
				} );
		yText.setText(graph.gy0 + " - " + graph.gy1);

		Box controls = new Box(BoxLayout.X_AXIS);
		controls.add(clearButton);
		controls.add(recButton);
		controls.add(playButton);
		controls.add(Box.createHorizontalGlue());
		controls.add(Box.createRigidArea(new Dimension(20, 0)));
		controls.add(sampleLabel);
		controls.add(sampleText);
		controls.add(thresholdLabel);
		controls.add(thresholdText);
		controls.add(Box.createHorizontalGlue());
		controls.add(Box.createRigidArea(new Dimension(20, 0)));
		controls.add(xControl);
		controls.add(yLabel);
		controls.add(yText);
		main.add(controls, BorderLayout.SOUTH);

		// The frame part
		frame = new JFrame("ContourTracking");
		frame.setSize(main.getPreferredSize());
		frame.getContentPane().add(main);
		frame.setVisible(true);
		frame.setLocationRelativeTo(null);
		frame.setExtendedState(JFrame.MAXIMIZED_BOTH); 
		frame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) { System.exit(0); }
		});
	}

	/* User operation: clear data */
	void clearData() {
		synchronized (parent) {
			moteListModel.clear();
			parent.clear();
			graph.newData();
			grid.repaint();
		}
	}

	/* User operation: set Y-axis range. */
	void setYAxis() {
		String val = yText.getText();

		try {
			int dash = val.indexOf('-');
			if (dash >= 0) {
				String min = val.substring(0, dash).trim();
				String max = val.substring(dash + 1).trim();

				if (!graph.setYAxis(Integer.parseInt(min), Integer.parseInt(max))) {
					error("Invalid range " 
							+ min 
							+ " - " 
							+ max 
							+ " (expected values between 0 and 65535)");
				}
				return;
			}
		}
		catch (NumberFormatException e) { }
		error("Invalid range " + val + " (expected NN-MM)");
	}

	/* User operation: set sample period. */
	void setSamplePeriod() {
		String periodS = sampleText.getText().trim();
		try {
			int newPeriod = Integer.parseInt(periodS);
			if (parent.setInterval(newPeriod)) {
				return;
			}
		}
		catch (NumberFormatException e) { }
		error("Invalid sample period " + periodS);
	}

	/* User operation: set sample threshold. */
	void setSampleThreshold() {
		String thresholdS = thresholdText.getText().trim();
		try {
			int newThreshold = Integer.parseInt(thresholdS);
			if (parent.setThreshold(newThreshold)) {
				return;
			}
		}
		catch (NumberFormatException e) { }
		error("Invalid sample threshold " + thresholdS);
	}

	/* Notification: sample period changed. */
	void updateSamplePeriod() {
		sampleText.setText("" + parent.interval);
	}

	/* Notification: sample threshold changed. */
	void updateSampleThreshold() {
		thresholdText.setText("" + parent.threshold);
	}

	/* Notification: new node. */
	void newNode(int nodeId) {
		moteListModel.newNode(nodeId);
	}

	/* Notification: new data. */
	void newData() {
		graph.newData();
		grid.repaint();
	}

	void error(String msg) {
		JOptionPane.showMessageDialog(frame, msg, "Error",
				JOptionPane.ERROR_MESSAGE);
	}
}
